/**************************************************************************
* Tint2 : blur.c
*
* Copyright (C) 2011       (mrovi@interfete-web-club.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**************************************************************************/


#include "blur.h"

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

void blurPixmap(Display *dsp, GC gc, Pixmap pix, int width, int height, int radius)
{
	/* The Pixmap needs to be converted to XImage to access the color data */
	XImage *xim;
	/* src, dst are temporary storage for the color matrix of the image
	 * The matrix is stored in src/dst as an array of width*height elements */
	unsigned long *src;
	unsigned long *dst;
	int i, j, k;
	/* Blur kernel (we only hold the central line because we blur in 2 passes) */
	int *kernel;
	/* The kernel size */
	int size = 2 * radius - 1;
	/* Sum of the elements of the kernel, used for luminance normalization */
	unsigned long norm;
	
	if (radius <= 1)
		return;
	
	/* Convert Pixmap to XImage */
	xim = XGetImage(dsp, pix, 0, 0, width, height, AllPlanes, ZPixmap);
	if (xim == NULL) {
		fprintf(stderr, "blurPixmap: could not convert Pixmap to XImage\n");
		return;
	}
	
	/* Create the src/dst matrices */
	src = (unsigned long *) malloc(width * height * sizeof(unsigned long));
	dst = (unsigned long *) malloc(width * height * sizeof(unsigned long));
	
	/* Copy the image data to src */
	for (i = 0; i < height; i++) {
		unsigned long *s = src + i * width;
		for (j = 0; j < width; j++) {
			s[j] = XGetPixel(xim, j, i);
		}
	}
	
	/* Create the kernel */
	kernel = (int *) malloc(size * sizeof(int));
	
	/* Typically the kernel is 6*sigma x 6*sigma, so use this to compute sigma
	 * from the size */
	norm = 0;
	double sigma = (size - 1) / 6.0;
	double sigma2d = 2 * sigma * sigma;
	for (i = 0; i < size; i++) {
		double x = i - radius + 1;
		kernel[i] = exp (- x * x / sigma2d) * 256;
		norm += kernel[i];
	}

	/* Horizontally blur from src -> dst */
	for (i = 0; i < height; i++) {
		unsigned long *s = src + i * width;
		unsigned long *d = dst + i * width;
		for (j = 0; j < width; j++) {
			/* The blurred color components */
			int r, g, b, a;
			r = g = b = a = 0;
			for (k = 0; k < size; k++) {
				/* Sample the original image (i, px) -> c */
				int px = j - radius + 1 + k;
				if (px < 0) {
					px = 0;
				} else if (px >= width) {
					px = width - 1;
				}
				int c = s[px];

				r += ((c >> 24) & 0xff) * kernel[k];
				g += ((c >> 16) & 0xff) * kernel[k];
				b += ((c >>  8) & 0xff) * kernel[k];
				a += ((c >>  0) & 0xff) * kernel[k];
			}
			r /= norm;
			g /= norm;
			b /= norm;
			a /= norm;
			d[j] = (r << 24) | (g << 16) | (b << 8) | a;
		}
	}

	/* Vertically blur from dst -> src */
	for (i = 0; i < height; i++) {
		unsigned long *d = src + i * width;
		for (j = 0; j < width; j++) {
			/* The blurred color components */
			int r, g, b, a;
			r = g = b = a = 0;
			for (k = 0; k < size; k++) {
				/* Sample the original image (py, j) -> c */
				int py = i - radius + 1 + k;
				if (py < 0) {
					py = 0;
				} else if (py >= height) {
					py = height - 1;
				}
				unsigned long *s = dst + py * width;
				int c = s[j];

				r += ((c >> 24) & 0xff) * kernel[k];
				g += ((c >> 16) & 0xff) * kernel[k];
				b += ((c >>  8) & 0xff) * kernel[k];
				a += ((c >>  0) & 0xff) * kernel[k];
			}
			r /= norm;
			g /= norm;
			b /= norm;
			a /= norm;
			d[j] = (r << 24) | (g << 16) | (b << 8) | a;
		}
	}
	
	/* Copy the image data from src to xim */
	for (i = 0; i < height; i++) {
		unsigned long *s = src + i * width;
		for (j = 0; j < width; j++) {
			XPutPixel(xim, j, i, s[j]);
		}
	}
	
	/* Write it back to the Pixmap */
	XPutImage(dsp, pix, gc, xim, 0, 0, 0, 0, width, height);
	
	free(src);
	free(dst);
	free(kernel);
	XDestroyImage(xim);
}
